mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-02-01 00:18:14 +01:00
Merge branch 'advplyr:master' into multi-select-keyboard-navigation
This commit is contained in:
commit
588def6d33
@ -56,7 +56,7 @@ export default {
|
|||||||
},
|
},
|
||||||
imgSrc() {
|
imgSrc() {
|
||||||
if (!this.imagePath) return null
|
if (!this.imagePath) return null
|
||||||
return `${this.$config.routerBasePath}/api/authors/${this.authorId}/image?token=${this.userToken}&ts=${this.updatedAt}`
|
return `${this.$config.routerBasePath}/api/authors/${this.authorId}/image?ts=${this.updatedAt}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -147,7 +147,7 @@ export default class LocalAudioPlayer extends EventEmitter {
|
|||||||
timeoutRetry: {
|
timeoutRetry: {
|
||||||
maxNumRetry: 4,
|
maxNumRetry: 4,
|
||||||
retryDelayMs: 0,
|
retryDelayMs: 0,
|
||||||
maxRetryDelayMs: 0,
|
maxRetryDelayMs: 0
|
||||||
},
|
},
|
||||||
errorRetry: {
|
errorRetry: {
|
||||||
maxNumRetry: 8,
|
maxNumRetry: 8,
|
||||||
@ -160,7 +160,7 @@ export default class LocalAudioPlayer extends EventEmitter {
|
|||||||
}
|
}
|
||||||
return retry
|
return retry
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -194,7 +194,7 @@ export default class LocalAudioPlayer extends EventEmitter {
|
|||||||
|
|
||||||
setDirectPlay() {
|
setDirectPlay() {
|
||||||
// Set initial track and track time offset
|
// Set initial track and track time offset
|
||||||
var trackIndex = this.audioTracks.findIndex(t => this.startTime >= t.startOffset && this.startTime < (t.startOffset + t.duration))
|
var trackIndex = this.audioTracks.findIndex((t) => this.startTime >= t.startOffset && this.startTime < t.startOffset + t.duration)
|
||||||
this.currentTrackIndex = trackIndex >= 0 ? trackIndex : 0
|
this.currentTrackIndex = trackIndex >= 0 ? trackIndex : 0
|
||||||
|
|
||||||
this.loadCurrentTrack()
|
this.loadCurrentTrack()
|
||||||
@ -270,7 +270,7 @@ export default class LocalAudioPlayer extends EventEmitter {
|
|||||||
// Seeking Direct play
|
// Seeking Direct play
|
||||||
if (time < this.currentTrack.startOffset || time > this.currentTrack.startOffset + this.currentTrack.duration) {
|
if (time < this.currentTrack.startOffset || time > this.currentTrack.startOffset + this.currentTrack.duration) {
|
||||||
// Change Track
|
// Change Track
|
||||||
var trackIndex = this.audioTracks.findIndex(t => time >= t.startOffset && time < (t.startOffset + t.duration))
|
var trackIndex = this.audioTracks.findIndex((t) => time >= t.startOffset && time < t.startOffset + t.duration)
|
||||||
if (trackIndex >= 0) {
|
if (trackIndex >= 0) {
|
||||||
this.startTime = time
|
this.startTime = time
|
||||||
this.currentTrackIndex = trackIndex
|
this.currentTrackIndex = trackIndex
|
||||||
@ -293,7 +293,6 @@ export default class LocalAudioPlayer extends EventEmitter {
|
|||||||
this.player.volume = volume
|
this.player.volume = volume
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
isValidDuration(duration) {
|
isValidDuration(duration) {
|
||||||
if (duration && !isNaN(duration) && duration !== Number.POSITIVE_INFINITY && duration !== Number.NEGATIVE_INFINITY) {
|
if (duration && !isNaN(duration) && duration !== Number.POSITIVE_INFINITY && duration !== Number.NEGATIVE_INFINITY) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const SupportedFileTypes = {
|
const SupportedFileTypes = {
|
||||||
image: ['png', 'jpg', 'jpeg', 'webp'],
|
image: ['png', 'jpg', 'jpeg', 'webp'],
|
||||||
audio: ['m4b', 'mp3', 'm4a', 'flac', 'opus', 'ogg', 'oga', 'mp4', 'aac', 'wma', 'aiff', 'wav', 'webm', 'webma', 'mka', 'awb', 'caf'],
|
audio: ['m4b', 'mp3', 'm4a', 'flac', 'opus', 'ogg', 'oga', 'mp4', 'aac', 'wma', 'aiff', 'wav', 'webm', 'webma', 'mka', 'awb', 'caf', 'mpeg', 'mpg'],
|
||||||
ebook: ['epub', 'pdf', 'mobi', 'azw3', 'cbr', 'cbz'],
|
ebook: ['epub', 'pdf', 'mobi', 'azw3', 'cbr', 'cbz'],
|
||||||
info: ['nfo'],
|
info: ['nfo'],
|
||||||
text: ['txt'],
|
text: ['txt'],
|
||||||
@ -81,9 +81,7 @@ const Hotkeys = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export { Constants }
|
||||||
Constants
|
|
||||||
}
|
|
||||||
export default ({ app }, inject) => {
|
export default ({ app }, inject) => {
|
||||||
inject('constants', Constants)
|
inject('constants', Constants)
|
||||||
inject('keynames', KeyNames)
|
inject('keynames', KeyNames)
|
||||||
|
@ -98,7 +98,7 @@ export const getters = {
|
|||||||
const userToken = rootGetters['user/getToken']
|
const userToken = rootGetters['user/getToken']
|
||||||
const lastUpdate = libraryItem.updatedAt || Date.now()
|
const lastUpdate = libraryItem.updatedAt || Date.now()
|
||||||
const libraryItemId = libraryItem.libraryItemId || libraryItem.id // Workaround for /users/:id page showing media progress covers
|
const libraryItemId = libraryItem.libraryItemId || libraryItem.id // Workaround for /users/:id page showing media progress covers
|
||||||
return `${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}&ts=${lastUpdate}${raw ? '&raw=1' : ''}`
|
return `${rootState.routerBasePath}/api/items/${libraryItemId}/cover?ts=${lastUpdate}${raw ? '&raw=1' : ''}`
|
||||||
},
|
},
|
||||||
getLibraryItemCoverSrcById:
|
getLibraryItemCoverSrcById:
|
||||||
(state, getters, rootState, rootGetters) =>
|
(state, getters, rootState, rootGetters) =>
|
||||||
@ -106,7 +106,7 @@ export const getters = {
|
|||||||
const placeholder = `${rootState.routerBasePath}/book_placeholder.jpg`
|
const placeholder = `${rootState.routerBasePath}/book_placeholder.jpg`
|
||||||
if (!libraryItemId) return placeholder
|
if (!libraryItemId) return placeholder
|
||||||
const userToken = rootGetters['user/getToken']
|
const userToken = rootGetters['user/getToken']
|
||||||
return `${rootState.routerBasePath}/api/items/${libraryItemId}/cover?token=${userToken}${raw ? '&raw=1' : ''}${timestamp ? `&ts=${timestamp}` : ''}`
|
return `${rootState.routerBasePath}/api/items/${libraryItemId}/cover?${raw ? '&raw=1' : ''}${timestamp ? `&ts=${timestamp}` : ''}`
|
||||||
},
|
},
|
||||||
getIsBatchSelectingMediaItems: (state) => {
|
getIsBatchSelectingMediaItems: (state) => {
|
||||||
return state.selectedMediaItems.length
|
return state.selectedMediaItems.length
|
||||||
|
@ -18,6 +18,26 @@ class Auth {
|
|||||||
constructor() {
|
constructor() {
|
||||||
// Map of openId sessions indexed by oauth2 state-variable
|
// Map of openId sessions indexed by oauth2 state-variable
|
||||||
this.openIdAuthSession = new Map()
|
this.openIdAuthSession = new Map()
|
||||||
|
this.ignorePatterns = [/\/api\/items\/[^/]+\/cover/, /\/api\/authors\/[^/]+\/image/]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the request should not be authenticated.
|
||||||
|
* @param {Request} req
|
||||||
|
* @returns {boolean}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
authNotNeeded(req) {
|
||||||
|
return req.method === 'GET' && this.ignorePatterns.some((pattern) => pattern.test(req.originalUrl))
|
||||||
|
}
|
||||||
|
|
||||||
|
ifAuthNeeded(middleware) {
|
||||||
|
return (req, res, next) => {
|
||||||
|
if (this.authNotNeeded(req)) {
|
||||||
|
return next()
|
||||||
|
}
|
||||||
|
middleware(req, res, next)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -238,7 +238,7 @@ class Server {
|
|||||||
// init passport.js
|
// init passport.js
|
||||||
app.use(passport.initialize())
|
app.use(passport.initialize())
|
||||||
// register passport in express-session
|
// register passport in express-session
|
||||||
app.use(passport.session())
|
app.use(this.auth.ifAuthNeeded(passport.session()))
|
||||||
// config passport.js
|
// config passport.js
|
||||||
await this.auth.initPassportJs()
|
await this.auth.initPassportJs()
|
||||||
|
|
||||||
@ -268,6 +268,10 @@ class Server {
|
|||||||
router.use(express.urlencoded({ extended: true, limit: '5mb' }))
|
router.use(express.urlencoded({ extended: true, limit: '5mb' }))
|
||||||
router.use(express.json({ limit: '5mb' }))
|
router.use(express.json({ limit: '5mb' }))
|
||||||
|
|
||||||
|
router.use('/api', this.auth.ifAuthNeeded(this.authMiddleware.bind(this)), this.apiRouter.router)
|
||||||
|
router.use('/hls', this.authMiddleware.bind(this), this.hlsRouter.router)
|
||||||
|
router.use('/public', this.publicRouter.router)
|
||||||
|
|
||||||
// Static path to generated nuxt
|
// Static path to generated nuxt
|
||||||
const distPath = Path.join(global.appRoot, '/client/dist')
|
const distPath = Path.join(global.appRoot, '/client/dist')
|
||||||
router.use(express.static(distPath))
|
router.use(express.static(distPath))
|
||||||
@ -275,10 +279,6 @@ class Server {
|
|||||||
// Static folder
|
// Static folder
|
||||||
router.use(express.static(Path.join(global.appRoot, 'static')))
|
router.use(express.static(Path.join(global.appRoot, 'static')))
|
||||||
|
|
||||||
router.use('/api', this.authMiddleware.bind(this), this.apiRouter.router)
|
|
||||||
router.use('/hls', this.authMiddleware.bind(this), this.hlsRouter.router)
|
|
||||||
router.use('/public', this.publicRouter.router)
|
|
||||||
|
|
||||||
// RSS Feed temp route
|
// RSS Feed temp route
|
||||||
router.get('/feed/:slug', (req, res) => {
|
router.get('/feed/:slug', (req, res) => {
|
||||||
Logger.info(`[Server] Requesting rss feed ${req.params.slug}`)
|
Logger.info(`[Server] Requesting rss feed ${req.params.slug}`)
|
||||||
@ -296,7 +296,7 @@ class Server {
|
|||||||
await this.auth.initAuthRoutes(router)
|
await this.auth.initAuthRoutes(router)
|
||||||
|
|
||||||
// Client dynamic routes
|
// Client dynamic routes
|
||||||
const dyanimicRoutes = [
|
const dynamicRoutes = [
|
||||||
'/item/:id',
|
'/item/:id',
|
||||||
'/author/:id',
|
'/author/:id',
|
||||||
'/audiobook/:id/chapters',
|
'/audiobook/:id/chapters',
|
||||||
@ -319,7 +319,7 @@ class Server {
|
|||||||
'/playlist/:id',
|
'/playlist/:id',
|
||||||
'/share/:slug'
|
'/share/:slug'
|
||||||
]
|
]
|
||||||
dyanimicRoutes.forEach((route) => router.get(route, (req, res) => res.sendFile(Path.join(distPath, 'index.html'))))
|
dynamicRoutes.forEach((route) => router.get(route, (req, res) => res.sendFile(Path.join(distPath, 'index.html'))))
|
||||||
|
|
||||||
router.post('/init', (req, res) => {
|
router.post('/init', (req, res) => {
|
||||||
if (Database.hasRootUser) {
|
if (Database.hasRootUser) {
|
||||||
|
@ -381,16 +381,23 @@ class AuthorController {
|
|||||||
*/
|
*/
|
||||||
async getImage(req, res) {
|
async getImage(req, res) {
|
||||||
const {
|
const {
|
||||||
query: { width, height, format, raw },
|
query: { width, height, format, raw }
|
||||||
author
|
|
||||||
} = req
|
} = req
|
||||||
|
|
||||||
|
const authorId = req.params.id
|
||||||
|
|
||||||
|
if (raw) {
|
||||||
|
const author = await Database.authorModel.findByPk(authorId)
|
||||||
|
if (!author) {
|
||||||
|
Logger.warn(`[AuthorController] Author "${authorId}" not found`)
|
||||||
|
return res.sendStatus(404)
|
||||||
|
}
|
||||||
|
|
||||||
if (!author.imagePath || !(await fs.pathExists(author.imagePath))) {
|
if (!author.imagePath || !(await fs.pathExists(author.imagePath))) {
|
||||||
Logger.warn(`[AuthorController] Author "${author.name}" has invalid imagePath: ${author.imagePath}`)
|
Logger.warn(`[AuthorController] Author "${author.name}" has invalid imagePath: ${author.imagePath}`)
|
||||||
return res.sendStatus(404)
|
return res.sendStatus(404)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (raw) {
|
|
||||||
return res.sendFile(author.imagePath)
|
return res.sendFile(author.imagePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -399,7 +406,7 @@ class AuthorController {
|
|||||||
height: height ? parseInt(height) : null,
|
height: height ? parseInt(height) : null,
|
||||||
width: width ? parseInt(width) : null
|
width: width ? parseInt(width) : null
|
||||||
}
|
}
|
||||||
return CacheManager.handleAuthorCache(res, author, options)
|
return CacheManager.handleAuthorCache(res, authorId, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -342,44 +342,25 @@ class LibraryItemController {
|
|||||||
query: { width, height, format, raw }
|
query: { width, height, format, raw }
|
||||||
} = req
|
} = req
|
||||||
|
|
||||||
const libraryItem = await Database.libraryItemModel.findByPk(req.params.id, {
|
|
||||||
attributes: ['id', 'mediaType', 'mediaId', 'libraryId'],
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
model: Database.bookModel,
|
|
||||||
attributes: ['id', 'coverPath', 'tags', 'explicit']
|
|
||||||
},
|
|
||||||
{
|
|
||||||
model: Database.podcastModel,
|
|
||||||
attributes: ['id', 'coverPath', 'tags', 'explicit']
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
if (!libraryItem) {
|
|
||||||
Logger.warn(`[LibraryItemController] getCover: Library item "${req.params.id}" does not exist`)
|
|
||||||
return res.sendStatus(404)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if user can access this library item
|
|
||||||
if (!req.user.checkCanAccessLibraryItem(libraryItem)) {
|
|
||||||
return res.sendStatus(403)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if library item media has a cover path
|
|
||||||
if (!libraryItem.media.coverPath || !(await fs.pathExists(libraryItem.media.coverPath))) {
|
|
||||||
return res.sendStatus(404)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.query.ts) res.set('Cache-Control', 'private, max-age=86400')
|
if (req.query.ts) res.set('Cache-Control', 'private, max-age=86400')
|
||||||
|
|
||||||
|
const libraryItemId = req.params.id
|
||||||
|
if (!libraryItemId) {
|
||||||
|
return res.sendStatus(400)
|
||||||
|
}
|
||||||
|
|
||||||
if (raw) {
|
if (raw) {
|
||||||
|
const coverPath = await Database.libraryItemModel.getCoverPath(libraryItemId)
|
||||||
|
if (!coverPath || !(await fs.pathExists(coverPath))) {
|
||||||
|
return res.sendStatus(404)
|
||||||
|
}
|
||||||
// any value
|
// any value
|
||||||
if (global.XAccel) {
|
if (global.XAccel) {
|
||||||
const encodedURI = encodeUriPath(global.XAccel + libraryItem.media.coverPath)
|
const encodedURI = encodeUriPath(global.XAccel + coverPath)
|
||||||
Logger.debug(`Use X-Accel to serve static file ${encodedURI}`)
|
Logger.debug(`Use X-Accel to serve static file ${encodedURI}`)
|
||||||
return res.status(204).header({ 'X-Accel-Redirect': encodedURI }).send()
|
return res.status(204).header({ 'X-Accel-Redirect': encodedURI }).send()
|
||||||
}
|
}
|
||||||
return res.sendFile(libraryItem.media.coverPath)
|
return res.sendFile(coverPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
@ -387,7 +368,7 @@ class LibraryItemController {
|
|||||||
height: height ? parseInt(height) : null,
|
height: height ? parseInt(height) : null,
|
||||||
width: width ? parseInt(width) : null
|
width: width ? parseInt(width) : null
|
||||||
}
|
}
|
||||||
return CacheManager.handleCoverCache(res, libraryItem.id, libraryItem.media.coverPath, options)
|
return CacheManager.handleCoverCache(res, libraryItemId, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4,6 +4,7 @@ const stream = require('stream')
|
|||||||
const Logger = require('../Logger')
|
const Logger = require('../Logger')
|
||||||
const { resizeImage } = require('../utils/ffmpegHelpers')
|
const { resizeImage } = require('../utils/ffmpegHelpers')
|
||||||
const { encodeUriPath } = require('../utils/fileUtils')
|
const { encodeUriPath } = require('../utils/fileUtils')
|
||||||
|
const Database = require('../Database')
|
||||||
|
|
||||||
class CacheManager {
|
class CacheManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -29,24 +30,24 @@ class CacheManager {
|
|||||||
await fs.ensureDir(this.ItemCachePath)
|
await fs.ensureDir(this.ItemCachePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleCoverCache(res, libraryItemId, coverPath, options = {}) {
|
async handleCoverCache(res, libraryItemId, options = {}) {
|
||||||
const format = options.format || 'webp'
|
const format = options.format || 'webp'
|
||||||
const width = options.width || 400
|
const width = options.width || 400
|
||||||
const height = options.height || null
|
const height = options.height || null
|
||||||
|
|
||||||
res.type(`image/${format}`)
|
res.type(`image/${format}`)
|
||||||
|
|
||||||
const path = Path.join(this.CoverCachePath, `${libraryItemId}_${width}${height ? `x${height}` : ''}`) + '.' + format
|
const cachePath = Path.join(this.CoverCachePath, `${libraryItemId}_${width}${height ? `x${height}` : ''}`) + '.' + format
|
||||||
|
|
||||||
// Cache exists
|
// Cache exists
|
||||||
if (await fs.pathExists(path)) {
|
if (await fs.pathExists(cachePath)) {
|
||||||
if (global.XAccel) {
|
if (global.XAccel) {
|
||||||
const encodedURI = encodeUriPath(global.XAccel + path)
|
const encodedURI = encodeUriPath(global.XAccel + cachePath)
|
||||||
Logger.debug(`Use X-Accel to serve static file ${encodedURI}`)
|
Logger.debug(`Use X-Accel to serve static file ${encodedURI}`)
|
||||||
return res.status(204).header({ 'X-Accel-Redirect': encodedURI }).send()
|
return res.status(204).header({ 'X-Accel-Redirect': encodedURI }).send()
|
||||||
}
|
}
|
||||||
|
|
||||||
const r = fs.createReadStream(path)
|
const r = fs.createReadStream(cachePath)
|
||||||
const ps = new stream.PassThrough()
|
const ps = new stream.PassThrough()
|
||||||
stream.pipeline(r, ps, (err) => {
|
stream.pipeline(r, ps, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -57,7 +58,13 @@ class CacheManager {
|
|||||||
return ps.pipe(res)
|
return ps.pipe(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
const writtenFile = await resizeImage(coverPath, path, width, height)
|
// Cached cover does not exist, generate it
|
||||||
|
const coverPath = await Database.libraryItemModel.getCoverPath(libraryItemId)
|
||||||
|
if (!coverPath || !(await fs.pathExists(coverPath))) {
|
||||||
|
return res.sendStatus(404)
|
||||||
|
}
|
||||||
|
|
||||||
|
const writtenFile = await resizeImage(coverPath, cachePath, width, height)
|
||||||
if (!writtenFile) return res.sendStatus(500)
|
if (!writtenFile) return res.sendStatus(500)
|
||||||
|
|
||||||
if (global.XAccel) {
|
if (global.XAccel) {
|
||||||
@ -127,22 +134,22 @@ class CacheManager {
|
|||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {import('express').Response} res
|
* @param {import('express').Response} res
|
||||||
* @param {import('../models/Author')} author
|
* @param {String} authorId
|
||||||
* @param {{ format?: string, width?: number, height?: number }} options
|
* @param {{ format?: string, width?: number, height?: number }} options
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async handleAuthorCache(res, author, options = {}) {
|
async handleAuthorCache(res, authorId, options = {}) {
|
||||||
const format = options.format || 'webp'
|
const format = options.format || 'webp'
|
||||||
const width = options.width || 400
|
const width = options.width || 400
|
||||||
const height = options.height || null
|
const height = options.height || null
|
||||||
|
|
||||||
res.type(`image/${format}`)
|
res.type(`image/${format}`)
|
||||||
|
|
||||||
var path = Path.join(this.ImageCachePath, `${author.id}_${width}${height ? `x${height}` : ''}`) + '.' + format
|
var cachePath = Path.join(this.ImageCachePath, `${authorId}_${width}${height ? `x${height}` : ''}`) + '.' + format
|
||||||
|
|
||||||
// Cache exists
|
// Cache exists
|
||||||
if (await fs.pathExists(path)) {
|
if (await fs.pathExists(cachePath)) {
|
||||||
const r = fs.createReadStream(path)
|
const r = fs.createReadStream(cachePath)
|
||||||
const ps = new stream.PassThrough()
|
const ps = new stream.PassThrough()
|
||||||
stream.pipeline(r, ps, (err) => {
|
stream.pipeline(r, ps, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -153,7 +160,12 @@ class CacheManager {
|
|||||||
return ps.pipe(res)
|
return ps.pipe(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
let writtenFile = await resizeImage(author.imagePath, path, width, height)
|
const author = await Database.authorModel.findByPk(authorId)
|
||||||
|
if (!author || !author.imagePath || !(await fs.pathExists(author.imagePath))) {
|
||||||
|
return res.sendStatus(404)
|
||||||
|
}
|
||||||
|
|
||||||
|
let writtenFile = await resizeImage(author.imagePath, cachePath, width, height)
|
||||||
if (!writtenFile) return res.sendStatus(500)
|
if (!writtenFile) return res.sendStatus(500)
|
||||||
|
|
||||||
var readStream = fs.createReadStream(writtenFile)
|
var readStream = fs.createReadStream(writtenFile)
|
||||||
|
@ -863,6 +863,33 @@ class LibraryItem extends Model {
|
|||||||
return this.getOldLibraryItem(libraryItem)
|
return this.getOldLibraryItem(libraryItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} libraryItemId
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
static async getCoverPath(libraryItemId) {
|
||||||
|
const libraryItem = await this.findByPk(libraryItemId, {
|
||||||
|
attributes: ['id', 'mediaType', 'mediaId', 'libraryId'],
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.book,
|
||||||
|
attributes: ['id', 'coverPath']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model: this.sequelize.models.podcast,
|
||||||
|
attributes: ['id', 'coverPath']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
if (!libraryItem) {
|
||||||
|
Logger.warn(`[LibraryItem] getCoverPath: Library item "${libraryItemId}" does not exist`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return libraryItem.media.coverPath
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {import('sequelize').FindOptions} options
|
* @param {import('sequelize').FindOptions} options
|
||||||
|
@ -216,7 +216,7 @@ class ApiRouter {
|
|||||||
this.router.patch('/authors/:id', AuthorController.middleware.bind(this), AuthorController.update.bind(this))
|
this.router.patch('/authors/:id', AuthorController.middleware.bind(this), AuthorController.update.bind(this))
|
||||||
this.router.delete('/authors/:id', AuthorController.middleware.bind(this), AuthorController.delete.bind(this))
|
this.router.delete('/authors/:id', AuthorController.middleware.bind(this), AuthorController.delete.bind(this))
|
||||||
this.router.post('/authors/:id/match', AuthorController.middleware.bind(this), AuthorController.match.bind(this))
|
this.router.post('/authors/:id/match', AuthorController.middleware.bind(this), AuthorController.match.bind(this))
|
||||||
this.router.get('/authors/:id/image', AuthorController.middleware.bind(this), AuthorController.getImage.bind(this))
|
this.router.get('/authors/:id/image', AuthorController.getImage.bind(this))
|
||||||
this.router.post('/authors/:id/image', AuthorController.middleware.bind(this), AuthorController.uploadImage.bind(this))
|
this.router.post('/authors/:id/image', AuthorController.middleware.bind(this), AuthorController.uploadImage.bind(this))
|
||||||
this.router.delete('/authors/:id/image', AuthorController.middleware.bind(this), AuthorController.deleteImage.bind(this))
|
this.router.delete('/authors/:id/image', AuthorController.middleware.bind(this), AuthorController.deleteImage.bind(this))
|
||||||
|
|
||||||
|
@ -49,5 +49,7 @@ module.exports.AudioMimeType = {
|
|||||||
WEBMA: 'audio/webm',
|
WEBMA: 'audio/webm',
|
||||||
MKA: 'audio/x-matroska',
|
MKA: 'audio/x-matroska',
|
||||||
AWB: 'audio/amr-wb',
|
AWB: 'audio/amr-wb',
|
||||||
CAF: 'audio/x-caf'
|
CAF: 'audio/x-caf',
|
||||||
|
MPEG: 'audio/mpeg',
|
||||||
|
MPG: 'audio/mpeg'
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
const globals = {
|
const globals = {
|
||||||
SupportedImageTypes: ['png', 'jpg', 'jpeg', 'webp'],
|
SupportedImageTypes: ['png', 'jpg', 'jpeg', 'webp'],
|
||||||
SupportedAudioTypes: ['m4b', 'mp3', 'm4a', 'flac', 'opus', 'ogg', 'oga', 'mp4', 'aac', 'wma', 'aiff', 'wav', 'webm', 'webma', 'mka', 'awb', 'caf'],
|
SupportedAudioTypes: ['m4b', 'mp3', 'm4a', 'flac', 'opus', 'ogg', 'oga', 'mp4', 'aac', 'wma', 'aiff', 'wav', 'webm', 'webma', 'mka', 'awb', 'caf', 'mpg', 'mpeg'],
|
||||||
SupportedEbookTypes: ['epub', 'pdf', 'mobi', 'azw3', 'cbr', 'cbz'],
|
SupportedEbookTypes: ['epub', 'pdf', 'mobi', 'azw3', 'cbr', 'cbz'],
|
||||||
TextFileTypes: ['txt', 'nfo'],
|
TextFileTypes: ['txt', 'nfo'],
|
||||||
MetadataFileTypes: ['opf', 'abs', 'xml', 'json']
|
MetadataFileTypes: ['opf', 'abs', 'xml', 'json']
|
||||||
|
@ -52,6 +52,13 @@ module.exports.parse = (nameString) => {
|
|||||||
}
|
}
|
||||||
if (splitNames.length) splitNames = splitNames.map((a) => a.trim())
|
if (splitNames.length) splitNames = splitNames.map((a) => a.trim())
|
||||||
|
|
||||||
|
// If names are in Chinese,Japanese and Korean languages, return as is.
|
||||||
|
if (/[\u4e00-\u9fff\u3040-\u30ff\u31f0-\u31ff]/.test(splitNames[0])) {
|
||||||
|
return {
|
||||||
|
names: splitNames
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var names = []
|
var names = []
|
||||||
|
|
||||||
// 1 name FIRST LAST
|
// 1 name FIRST LAST
|
||||||
|
Loading…
Reference in New Issue
Block a user