mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	Merge pull request #240 from keaganhilliard/master
Remove sharp in favor of ffmpeg, fallback to cover
This commit is contained in:
		
						commit
						87216bbd88
					
				@ -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",
 | 
			
		||||
 | 
			
		||||
@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
@ -92,4 +92,29 @@ async function extractCoverArt(filepath, outputpath) {
 | 
			
		||||
    ffmpeg.run()
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
module.exports.extractCoverArt = extractCoverArt
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user