2021-10-02 01:42:48 +02:00
|
|
|
const fs = require('fs-extra')
|
|
|
|
const Path = require('path')
|
|
|
|
const axios = require('axios')
|
|
|
|
const Logger = require('./Logger')
|
|
|
|
const readChunk = require('read-chunk')
|
|
|
|
const imageType = require('image-type')
|
|
|
|
|
|
|
|
const globals = require('./utils/globals')
|
|
|
|
const { CoverDestination } = require('./utils/constants')
|
|
|
|
|
|
|
|
class CoverController {
|
|
|
|
constructor(db, MetadataPath, AudiobookPath) {
|
|
|
|
this.db = db
|
2021-11-06 23:26:44 +01:00
|
|
|
this.MetadataPath = MetadataPath.replace(/\\/g, '/')
|
|
|
|
this.BookMetadataPath = Path.posix.join(this.MetadataPath, 'books')
|
2021-10-02 01:42:48 +02:00
|
|
|
this.AudiobookPath = AudiobookPath
|
|
|
|
}
|
|
|
|
|
|
|
|
getCoverDirectory(audiobook) {
|
|
|
|
if (this.db.serverSettings.coverDestination === CoverDestination.AUDIOBOOK) {
|
|
|
|
return {
|
|
|
|
fullPath: audiobook.fullPath,
|
2021-10-05 05:11:42 +02:00
|
|
|
relPath: '/s/book/' + audiobook.id
|
2021-10-02 01:42:48 +02:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return {
|
2021-11-06 23:26:44 +01:00
|
|
|
fullPath: Path.posix.join(this.BookMetadataPath, audiobook.id),
|
|
|
|
relPath: Path.posix.join('/metadata', 'books', audiobook.id)
|
2021-10-02 01:42:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getFilesInDirectory(dir) {
|
|
|
|
try {
|
|
|
|
return fs.readdir(dir)
|
|
|
|
} catch (error) {
|
|
|
|
Logger.error(`[CoverController] Failed to get files in dir ${dir}`, error)
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
removeFile(filepath) {
|
|
|
|
try {
|
|
|
|
return fs.pathExists(filepath).then((exists) => {
|
|
|
|
if (!exists) Logger.warn(`[CoverController] Attempting to remove file that does not exist ${filepath}`)
|
|
|
|
return exists ? fs.unlink(filepath) : false
|
|
|
|
})
|
|
|
|
} catch (error) {
|
|
|
|
Logger.error(`[CoverController] Failed to remove file "${filepath}"`, error)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-02 03:29:00 +02:00
|
|
|
// Remove covers that dont have the same filename as the new cover
|
|
|
|
async removeOldCovers(dirpath, newCoverExt) {
|
2021-10-02 01:42:48 +02:00
|
|
|
var filesInDir = await this.getFilesInDirectory(dirpath)
|
|
|
|
|
|
|
|
for (let i = 0; i < filesInDir.length; i++) {
|
|
|
|
var file = filesInDir[i]
|
|
|
|
var _extname = Path.extname(file)
|
|
|
|
var _filename = Path.basename(file, _extname)
|
|
|
|
if (_filename === 'cover' && _extname !== newCoverExt) {
|
|
|
|
var filepath = Path.join(dirpath, file)
|
|
|
|
Logger.debug(`[CoverController] Removing old cover from metadata "${filepath}"`)
|
|
|
|
await this.removeFile(filepath)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async checkFileIsValidImage(imagepath) {
|
|
|
|
const buffer = await readChunk(imagepath, 0, 12)
|
|
|
|
const imgType = imageType(buffer)
|
|
|
|
if (!imgType) {
|
|
|
|
await this.removeFile(imagepath)
|
|
|
|
return {
|
|
|
|
error: 'Invalid image'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!globals.SupportedImageTypes.includes(imgType.ext)) {
|
|
|
|
await this.removeFile(imagepath)
|
|
|
|
return {
|
|
|
|
error: `Invalid image type ${imgType.ext} (Supported: ${globals.SupportedImageTypes.join(',')})`
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return imgType
|
|
|
|
}
|
|
|
|
|
|
|
|
async uploadCover(audiobook, coverFile) {
|
|
|
|
var extname = Path.extname(coverFile.name.toLowerCase())
|
|
|
|
if (!extname || !globals.SupportedImageTypes.includes(extname.slice(1))) {
|
|
|
|
return {
|
|
|
|
error: `Invalid image type ${extname} (Supported: ${globals.SupportedImageTypes.join(',')})`
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var { fullPath, relPath } = this.getCoverDirectory(audiobook)
|
|
|
|
await fs.ensureDir(fullPath)
|
|
|
|
|
|
|
|
var coverFilename = `cover${extname}`
|
2021-11-06 23:26:44 +01:00
|
|
|
var coverFullPath = Path.posix.join(fullPath, coverFilename)
|
|
|
|
var coverPath = Path.posix.join(relPath, coverFilename)
|
2021-10-02 01:42:48 +02:00
|
|
|
|
|
|
|
// Move cover from temp upload dir to destination
|
|
|
|
var success = await coverFile.mv(coverFullPath).then(() => true).catch((error) => {
|
|
|
|
Logger.error('[CoverController] Failed to move cover file', path, error)
|
|
|
|
return false
|
|
|
|
})
|
|
|
|
|
|
|
|
if (!success) {
|
|
|
|
return {
|
|
|
|
error: 'Failed to move cover into destination'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-02 03:29:00 +02:00
|
|
|
await this.removeOldCovers(fullPath, extname)
|
|
|
|
|
2021-10-02 01:42:48 +02:00
|
|
|
Logger.info(`[CoverController] Uploaded audiobook cover "${coverPath}" for "${audiobook.title}"`)
|
|
|
|
|
2021-10-06 04:10:49 +02:00
|
|
|
audiobook.updateBookCover(coverPath, coverFullPath)
|
2021-10-02 01:42:48 +02:00
|
|
|
return {
|
|
|
|
cover: coverPath
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async downloadFile(url, filepath) {
|
|
|
|
Logger.debug(`[CoverController] Starting file download to ${filepath}`)
|
|
|
|
const writer = fs.createWriteStream(filepath)
|
|
|
|
const response = await axios({
|
|
|
|
url,
|
|
|
|
method: 'GET',
|
|
|
|
responseType: 'stream'
|
|
|
|
})
|
|
|
|
response.data.pipe(writer)
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
writer.on('finish', resolve)
|
|
|
|
writer.on('error', reject)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
async downloadCoverFromUrl(audiobook, url) {
|
|
|
|
try {
|
|
|
|
var { fullPath, relPath } = this.getCoverDirectory(audiobook)
|
|
|
|
await fs.ensureDir(fullPath)
|
|
|
|
|
2021-11-06 23:26:44 +01:00
|
|
|
var temppath = Path.posix.join(fullPath, 'cover')
|
2021-10-02 01:42:48 +02:00
|
|
|
var success = await this.downloadFile(url, temppath).then(() => true).catch((err) => {
|
|
|
|
Logger.error(`[CoverController] Download image file failed for "${url}"`, err)
|
|
|
|
return false
|
|
|
|
})
|
|
|
|
if (!success) {
|
|
|
|
return {
|
|
|
|
error: 'Failed to download image from url'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var imgtype = await this.checkFileIsValidImage(temppath)
|
|
|
|
|
|
|
|
if (imgtype.error) {
|
|
|
|
return imgtype
|
|
|
|
}
|
|
|
|
|
|
|
|
var coverFilename = `cover.${imgtype.ext}`
|
2021-11-06 23:26:44 +01:00
|
|
|
var coverPath = Path.posix.join(relPath, coverFilename)
|
|
|
|
var coverFullPath = Path.posix.join(fullPath, coverFilename)
|
2021-10-02 01:42:48 +02:00
|
|
|
await fs.rename(temppath, coverFullPath)
|
|
|
|
|
2021-10-02 03:29:00 +02:00
|
|
|
await this.removeOldCovers(fullPath, '.' + imgtype.ext)
|
2021-10-02 01:42:48 +02:00
|
|
|
|
|
|
|
Logger.info(`[CoverController] Downloaded audiobook cover "${coverPath}" from url "${url}" for "${audiobook.title}"`)
|
|
|
|
|
2021-10-06 04:10:49 +02:00
|
|
|
audiobook.updateBookCover(coverPath, coverFullPath)
|
2021-10-02 01:42:48 +02:00
|
|
|
return {
|
|
|
|
cover: coverPath
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
Logger.error(`[CoverController] Fetch cover image from url "${url}" failed`, error)
|
|
|
|
return {
|
|
|
|
error: 'Failed to fetch image from url'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
module.exports = CoverController
|