diff --git a/package.json b/package.json index 8c5cfd63..6ef5ada4 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,6 @@ "podcast": "^1.3.0", "read-chunk": "^3.1.0", "recursive-readdir-async": "^1.1.8", - "sharp": "^0.29.3", "socket.io": "^4.1.3", "string-strip-html": "^8.3.0", "watcher": "^1.2.0", diff --git a/server/CacheManager.js b/server/CacheManager.js index c31a25f9..6fe3d9e2 100644 --- a/server/CacheManager.js +++ b/server/CacheManager.js @@ -1,8 +1,8 @@ const Path = require('path') const fs = require('fs-extra') const stream = require('stream') -const resize = require('./utils/resizeImage') const Logger = require('./Logger') +const { resizeImage } = require('./utils/ffmpegHelpers') class CacheManager { constructor(MetadataPath) { @@ -18,7 +18,7 @@ class CacheManager { res.type(`image/${format}`) - var path = Path.join(this.CoverCachePath, audiobook.id) + '.' + format + var path = Path.join(this.CoverCachePath, `${audiobook.id}_${width}${height ? `x${height}` : ''}`) + '.' + format // Cache exists if (await fs.pathExists(path)) { @@ -35,22 +35,22 @@ class CacheManager { // Write cache await fs.ensureDir(this.CoverCachePath) - var readStream = resize(audiobook.book.coverFullPath, width, height, format) - var writeStream = fs.createWriteStream(path) - writeStream.on('error', (e) => { - Logger.error(`[CacheManager] Cache write error ${e.message}`) - }) - readStream.pipe(writeStream) + let writtenFile = await resizeImage(audiobook.book.coverFullPath, path, width, height) + var readStream = fs.createReadStream(writtenFile) readStream.pipe(res) } - purgeCoverCache(audiobookId) { - var basepath = Path.join(this.CoverCachePath, audiobookId) - // Remove both webp and jpg caches if exist - var webpPath = basepath + '.webp' - var jpgPath = basepath + '.jpg' - return Promise.all([this.removeCache(webpPath), this.removeCache(jpgPath)]) + async purgeCoverCache(audiobookId) { + // If purgeAll has been called... The cover cache directory no longer exists + await fs.ensureDir(this.CoverCachePath) + return Promise.all((await fs.readdir(this.CoverCachePath)).reduce((promises, file) => { + if (file.startsWith(audiobookId)) { + Logger.debug(`[CacheManager] Going to purge ${file}`); + promises.push(this.removeCache(Path.join(this.CoverCachePath, file))) + } + return promises + }, [])) } removeCache(path) { diff --git a/server/controllers/BookController.js b/server/controllers/BookController.js index 3fa22136..39ae624c 100644 --- a/server/controllers/BookController.js +++ b/server/controllers/BookController.js @@ -234,15 +234,25 @@ class BookController { async getCover(req, res) { let { query: { width, height, format }, params: { id } } = req var audiobook = this.db.audiobooks.find(a => a.id === id) - if (!audiobook || !audiobook.book.coverFullPath) return res.sendStatus(404) + if (!audiobook || !audiobook.book.cover) return res.sendStatus(404) // Check user can access this audiobooks library if (!req.user.checkCanAccessLibrary(audiobook.libraryId)) { return res.sendStatus(403) } + // Temp fix for books without a full cover path + if (audiobook.book.cover && !audiobook.book.coverFullPath) { + var isFixed = audiobook.fixFullCoverPath() + if (!isFixed) { + Logger.warn(`[BookController] Failed to fix full cover path "${audiobook.book.cover}" for "${audiobook.book.title}"`) + return res.sendStatus(404) + } + await this.db.updateEntity('audiobook', audiobook) + } + const options = { - format: format || (reqSupportsWebp(req) ? 'webp' : 'jpg'), + format: format || (reqSupportsWebp(req) ? 'webp' : 'jpeg'), height: height ? parseInt(height) : null, width: width ? parseInt(width) : null } diff --git a/server/objects/Audiobook.js b/server/objects/Audiobook.js index 419efc79..64a0c5a9 100644 --- a/server/objects/Audiobook.js +++ b/server/objects/Audiobook.js @@ -801,7 +801,7 @@ class Audiobook { var success = await extractCoverArt(audioFileWithCover.fullPath, coverFilePath) if (success) { var coverRelPath = Path.join(coverDirRelPath, coverFilename).replace(/\\/g, '/').replace(/\/\//g, '/') - this.update({ book: { cover: coverRelPath } }) + this.update({ book: { cover: coverRelPath, coverFullPath: audioFileWithCover.fullPath } }) return coverRelPath } return false @@ -1022,5 +1022,23 @@ class Audiobook { existingOtherFileData } } + + // Temp fix for cover is set but coverFullPath is not set + fixFullCoverPath(metadataPath) { + if (!this.book.cover) return + var bookCoverPath = this.book.cover.replace(/\\/g, '/') + var newFullCoverPath = null + if (bookCoverPath.startsWith('/s/book/')) { + newFullCoverPath = Path.join(this.fullPath, bookCoverPath.substr(`/s/book/${this.id}`.length)).replace(/\/\//g, '/') + } else if (bookCoverPath.startsWith('/metadata/')) { + newFullCoverPath = Path.join(metadataPath, bookCoverPath.substr('/metadata/'.length)).replace(/\/\//g, '/') + } + if (newFullCoverPath) { + Logger.debug(`[Audiobook] "${this.title}" fixing full cover path "${this.book.cover}" => "${newFullCoverPath}"`) + this.update({ book: { fullCoverPath: newFullCoverPath } }) + return true + } + return false + } } module.exports = Audiobook \ No newline at end of file diff --git a/server/utils/ffmpegHelpers.js b/server/utils/ffmpegHelpers.js index 9ba53364..0d92685a 100644 --- a/server/utils/ffmpegHelpers.js +++ b/server/utils/ffmpegHelpers.js @@ -92,4 +92,29 @@ async function extractCoverArt(filepath, outputpath) { ffmpeg.run() }) } -module.exports.extractCoverArt = extractCoverArt \ No newline at end of file +module.exports.extractCoverArt = extractCoverArt + +//This should convert based on the output file extension as well +async function resizeImage(filePath, outputPath, width, height) { + var dirname = Path.dirname(outputPath); + await fs.ensureDir(dirname); + + return new Promise((resolve) => { + var ffmpeg = Ffmpeg(filePath) + ffmpeg.addOption(['-vf', `scale=${width || -1}:${height || -1}`]) + ffmpeg.addOutput(outputPath) + ffmpeg.on('start', (cmd) => { + Logger.debug(`[FfmpegHelpers] Resize Image Cmd: ${cmd}`) + }) + ffmpeg.on('error', (err, stdout, stderr) => { + Logger.error(`[FfmpegHelpers] Resize Image Error ${err}`) + resolve(false) + }) + ffmpeg.on('end', () => { + Logger.debug(`[FfmpegHelpers] Image resized Successfully`) + resolve(outputPath) + }) + ffmpeg.run() + }) +} +module.exports.resizeImage = resizeImage diff --git a/server/utils/resizeImage.js b/server/utils/resizeImage.js deleted file mode 100644 index aea5adff..00000000 --- a/server/utils/resizeImage.js +++ /dev/null @@ -1,16 +0,0 @@ -const sharp = require('sharp') -const fs = require('fs') - -function resize(filePath, width, height, format = 'webp') { - const readStream = fs.createReadStream(filePath); - let sharpie = sharp() - sharpie.toFormat(format) - - if (width || height) { - sharpie.resize(width, height, { withoutEnlargement: true }) - } - - return readStream.pipe(sharpie) -} - -module.exports = resize; \ No newline at end of file