audiobookshelf/server/objects/Book.js

262 lines
7.9 KiB
JavaScript
Raw Normal View History

const fs = require('fs-extra')
const Path = require('path')
const Logger = require('../Logger')
const parseAuthors = require('../utils/parseAuthors')
2021-08-18 00:01:11 +02:00
class Book {
constructor(book = null) {
this.olid = null
this.title = null
this.subtitle = null
2021-08-18 00:01:11 +02:00
this.author = null
this.authorFL = null
this.authorLF = null
this.narrator = null
2021-08-19 18:31:03 +02:00
this.series = null
this.volumeNumber = null
2021-08-18 00:01:11 +02:00
this.publishYear = null
this.publisher = null
this.description = null
this.cover = null
this.coverFullPath = null
this.genres = []
2021-10-06 04:10:49 +02:00
this.lastUpdate = null
2021-08-18 00:01:11 +02:00
2021-10-06 04:10:49 +02:00
// Should not continue looking up a cover when it is not findable
this.lastCoverSearch = null
this.lastCoverSearchTitle = null
this.lastCoverSearchAuthor = null
2021-08-18 00:01:11 +02:00
if (book) {
this.construct(book)
}
}
get _title() { return this.title || '' }
get _subtitle() { return this.subtitle || '' }
get _narrator() { return this.narrator || '' }
get _author() { return this.author || '' }
get _series() { return this.series || '' }
2021-10-06 04:10:49 +02:00
get shouldSearchForCover() {
if (this.author !== this.lastCoverSearchAuthor || this.title !== this.lastCoverSearchTitle || !this.lastCoverSearch) return true
var timeSinceLastSearch = Date.now() - this.lastCoverSearch
return timeSinceLastSearch > 1000 * 60 * 60 * 24 * 7 // every 7 days do another lookup
}
2021-08-18 00:01:11 +02:00
construct(book) {
this.olid = book.olid
this.title = book.title
this.subtitle = book.subtitle || null
2021-08-18 00:01:11 +02:00
this.author = book.author
this.authorFL = book.authorFL || null
this.authorLF = book.authorLF || null
this.narrator = book.narrator || book.narrarator || null // Mispelled initially... need to catch those
2021-08-19 18:31:03 +02:00
this.series = book.series
this.volumeNumber = book.volumeNumber || null
this.publishYear = book.publishYear
2021-08-18 00:01:11 +02:00
this.publisher = book.publisher
this.description = book.description
this.cover = book.cover
this.coverFullPath = book.coverFullPath || null
this.genres = book.genres
this.lastUpdate = book.lastUpdate || Date.now()
2021-10-06 04:10:49 +02:00
this.lastCoverSearch = book.lastCoverSearch || null
this.lastCoverSearchTitle = book.lastCoverSearchTitle || null
this.lastCoverSearchAuthor = book.lastCoverSearchAuthor || null
2021-08-18 00:01:11 +02:00
}
toJSON() {
return {
olid: this.olid,
title: this.title,
subtitle: this.subtitle,
2021-08-18 00:01:11 +02:00
author: this.author,
authorFL: this.authorFL,
authorLF: this.authorLF,
narrator: this.narrator,
2021-08-19 18:31:03 +02:00
series: this.series,
volumeNumber: this.volumeNumber,
publishYear: this.publishYear,
2021-08-18 00:01:11 +02:00
publisher: this.publisher,
description: this.description,
cover: this.cover,
coverFullPath: this.coverFullPath,
genres: this.genres,
2021-10-06 04:10:49 +02:00
lastUpdate: this.lastUpdate,
lastCoverSearch: this.lastCoverSearch,
lastCoverSearchTitle: this.lastCoverSearchTitle,
lastCoverSearchAuthor: this.lastCoverSearchAuthor
2021-08-18 00:01:11 +02:00
}
}
setParseAuthor(author) {
if (!author) {
var hasUpdated = this.authorFL || this.authorLF
this.authorFL = null
this.authorLF = null
return hasUpdated
}
try {
var { authorLF, authorFL } = parseAuthors(author)
var hasUpdated = authorLF !== this.authorLF || authorFL !== this.authorFL
this.authorFL = authorFL || null
this.authorLF = authorLF || null
return hasUpdated
} catch (err) {
Logger.error('[Book] Parse authors failed', err)
return false
}
}
2021-08-18 00:01:11 +02:00
setData(data) {
this.olid = data.olid || null
this.title = data.title || null
this.subtitle = data.subtitle || null
2021-08-18 00:01:11 +02:00
this.author = data.author || null
this.narrator = data.narrator || data.narrarator || null
2021-08-19 18:31:03 +02:00
this.series = data.series || null
this.volumeNumber = data.volumeNumber || null
this.publishYear = data.publishYear || null
2021-08-18 00:01:11 +02:00
this.description = data.description || null
this.cover = data.cover || null
this.coverFullPath = data.coverFullPath || null
this.genres = data.genres || []
this.lastUpdate = Date.now()
2021-10-06 04:10:49 +02:00
this.lastCoverSearch = data.lastCoverSearch || null
this.lastCoverSearchTitle = data.lastCoverSearchTitle || null
this.lastCoverSearchAuthor = data.lastCoverSearchAuthor || null
if (data.author) {
this.setParseAuthor(this.author)
}
2021-08-18 00:01:11 +02:00
}
update(payload) {
var hasUpdates = false
if (payload.cover) {
// If updating to local cover then normalize path
if (!payload.cover.startsWith('http:') && !payload.cover.startsWith('https:')) {
payload.cover = Path.normalize(payload.cover)
2021-10-06 04:10:49 +02:00
if (payload.coverFullPath) payload.coverFullPath = Path.normalize(payload.coverFullPath)
}
}
2021-08-18 00:01:11 +02:00
for (const key in payload) {
if (payload[key] === undefined) continue;
if (key === 'genres') {
if (payload['genres'] === null && this.genres !== null) {
this.genres = []
2021-08-18 00:01:11 +02:00
hasUpdates = true
} else if (payload['genres'].join(',') !== this.genres.join(',')) {
this.genres = payload['genres']
2021-08-18 00:01:11 +02:00
hasUpdates = true
}
} else if (key === 'author') {
if (this.author !== payload.author) {
this.author = payload.author || null
hasUpdates = true
}
if (this.setParseAuthor(this.author)) {
hasUpdates = true
}
2021-08-18 00:01:11 +02:00
} else if (this[key] !== undefined && payload[key] !== this[key]) {
this[key] = payload[key]
hasUpdates = true
}
}
if (hasUpdates) {
this.lastUpdate = Date.now()
}
return hasUpdates
}
2021-10-06 04:10:49 +02:00
updateLastCoverSearch(coverWasFound) {
this.lastCoverSearch = coverWasFound ? null : Date.now()
this.lastCoverSearchAuthor = coverWasFound ? null : this.author
this.lastCoverSearchTitle = coverWasFound ? null : this.title
}
updateCover(cover, coverFullPath) {
if (!cover) return false
if (!cover.startsWith('http:') && !cover.startsWith('https:')) {
cover = Path.normalize(cover)
2021-10-06 04:10:49 +02:00
this.coverFullPath = Path.normalize(coverFullPath)
} else {
this.coverFullPath = cover
}
this.cover = cover
this.lastUpdate = Date.now()
return true
}
removeCover() {
this.cover = null
this.coverFullPath = null
this.lastUpdate = Date.now()
}
// If audiobook directory path was changed, check and update properties set from dirnames
// May be worthwhile checking if these were manually updated and not override manual updates
syncPathsUpdated(audiobookData) {
2021-09-18 18:13:05 +02:00
var keysToSync = ['author', 'title', 'series', 'publishYear', 'volumeNumber']
var syncPayload = {}
keysToSync.forEach((key) => {
if (audiobookData[key]) syncPayload[key] = audiobookData[key]
})
if (!Object.keys(syncPayload).length) return false
return this.update(syncPayload)
}
isSearchMatch(search) {
return this._title.toLowerCase().includes(search) || this._subtitle.toLowerCase().includes(search) || this._author.toLowerCase().includes(search) || this._series.toLowerCase().includes(search)
}
setDetailsFromFileMetadata(audioFileMetadata) {
const MetadataMapArray = [
{
tag: 'tagComposer',
key: 'narrator'
},
{
tag: 'tagDescription',
key: 'description'
},
{
tag: 'tagPublisher',
key: 'publisher'
},
{
tag: 'tagDate',
key: 'publishYear'
},
{
tag: 'tagSubtitle',
key: 'subtitle'
},
{
tag: 'tagArtist',
key: 'author'
}
]
var updatePayload = {}
MetadataMapArray.forEach((mapping) => {
if (!this[mapping.key] && audioFileMetadata[mapping.tag]) {
updatePayload[mapping.key] = audioFileMetadata[mapping.tag]
Logger.debug(`[Book] Mapping metadata to key ${mapping.tag} => ${mapping.key}: ${updatePayload[mapping.key]}`)
}
})
if (Object.keys(updatePayload).length) {
return this.update(updatePayload)
}
return false
}
2021-08-18 00:01:11 +02:00
}
module.exports = Book