2023-07-05 01:14:44 +02:00
|
|
|
const { DataTypes, Model } = require('sequelize')
|
|
|
|
const Logger = require('../Logger')
|
|
|
|
|
2023-08-25 00:55:29 +02:00
|
|
|
/**
|
|
|
|
* @typedef EBookFileObject
|
|
|
|
* @property {string} ino
|
|
|
|
* @property {string} ebookFormat
|
|
|
|
* @property {number} addedAt
|
|
|
|
* @property {number} updatedAt
|
2024-03-21 20:38:52 +01:00
|
|
|
* @property {{filename:string, ext:string, path:string, relPath:strFing, size:number, mtimeMs:number, ctimeMs:number, birthtimeMs:number}} metadata
|
2023-08-25 00:55:29 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @typedef ChapterObject
|
|
|
|
* @property {number} id
|
|
|
|
* @property {number} start
|
|
|
|
* @property {number} end
|
|
|
|
* @property {string} title
|
|
|
|
*/
|
|
|
|
|
2024-01-16 23:31:16 +01:00
|
|
|
/**
|
|
|
|
* @typedef SeriesExpandedProperties
|
|
|
|
* @property {{sequence:string}} bookSeries
|
2024-05-29 00:24:02 +02:00
|
|
|
*
|
2024-01-16 23:31:16 +01:00
|
|
|
* @typedef {import('./Series') & SeriesExpandedProperties} SeriesExpanded
|
2024-05-29 00:24:02 +02:00
|
|
|
*
|
2024-01-16 23:31:16 +01:00
|
|
|
* @typedef BookExpandedProperties
|
|
|
|
* @property {import('./Author')[]} authors
|
|
|
|
* @property {SeriesExpanded[]} series
|
2024-05-29 00:24:02 +02:00
|
|
|
*
|
2024-01-16 23:31:16 +01:00
|
|
|
* @typedef {Book & BookExpandedProperties} BookExpanded
|
|
|
|
*/
|
|
|
|
|
2023-08-28 00:19:57 +02:00
|
|
|
/**
|
|
|
|
* @typedef AudioFileObject
|
|
|
|
* @property {number} index
|
|
|
|
* @property {string} ino
|
|
|
|
* @property {{filename:string, ext:string, path:string, relPath:string, size:number, mtimeMs:number, ctimeMs:number, birthtimeMs:number}} metadata
|
|
|
|
* @property {number} addedAt
|
|
|
|
* @property {number} updatedAt
|
|
|
|
* @property {number} trackNumFromMeta
|
|
|
|
* @property {number} discNumFromMeta
|
|
|
|
* @property {number} trackNumFromFilename
|
|
|
|
* @property {number} discNumFromFilename
|
|
|
|
* @property {boolean} manuallyVerified
|
|
|
|
* @property {string} format
|
|
|
|
* @property {number} duration
|
|
|
|
* @property {number} bitRate
|
|
|
|
* @property {string} language
|
|
|
|
* @property {string} codec
|
|
|
|
* @property {string} timeBase
|
|
|
|
* @property {number} channels
|
|
|
|
* @property {string} channelLayout
|
|
|
|
* @property {ChapterObject[]} chapters
|
|
|
|
* @property {Object} metaTags
|
|
|
|
* @property {string} mimeType
|
|
|
|
*/
|
|
|
|
|
2023-08-16 01:03:43 +02:00
|
|
|
class Book extends Model {
|
|
|
|
constructor(values, options) {
|
|
|
|
super(values, options)
|
2023-07-29 01:03:31 +02:00
|
|
|
|
2023-08-25 00:55:29 +02:00
|
|
|
/** @type {string} */
|
2023-08-16 01:03:43 +02:00
|
|
|
this.id
|
|
|
|
/** @type {string} */
|
|
|
|
this.title
|
|
|
|
/** @type {string} */
|
|
|
|
this.titleIgnorePrefix
|
|
|
|
/** @type {string} */
|
2024-01-16 23:31:16 +01:00
|
|
|
this.subtitle
|
|
|
|
/** @type {string} */
|
2023-08-16 01:03:43 +02:00
|
|
|
this.publishedYear
|
|
|
|
/** @type {string} */
|
|
|
|
this.publishedDate
|
|
|
|
/** @type {string} */
|
|
|
|
this.publisher
|
|
|
|
/** @type {string} */
|
|
|
|
this.description
|
|
|
|
/** @type {string} */
|
|
|
|
this.isbn
|
|
|
|
/** @type {string} */
|
|
|
|
this.asin
|
|
|
|
/** @type {string} */
|
|
|
|
this.language
|
|
|
|
/** @type {boolean} */
|
|
|
|
this.explicit
|
|
|
|
/** @type {boolean} */
|
|
|
|
this.abridged
|
|
|
|
/** @type {string} */
|
|
|
|
this.coverPath
|
|
|
|
/** @type {number} */
|
|
|
|
this.duration
|
2023-08-25 00:55:29 +02:00
|
|
|
/** @type {string[]} */
|
2023-08-16 01:03:43 +02:00
|
|
|
this.narrators
|
2023-08-28 00:19:57 +02:00
|
|
|
/** @type {AudioFileObject[]} */
|
2023-08-16 01:03:43 +02:00
|
|
|
this.audioFiles
|
2023-08-25 00:55:29 +02:00
|
|
|
/** @type {EBookFileObject} */
|
2023-08-16 01:03:43 +02:00
|
|
|
this.ebookFile
|
2023-08-25 00:55:29 +02:00
|
|
|
/** @type {ChapterObject[]} */
|
2023-08-16 01:03:43 +02:00
|
|
|
this.chapters
|
2023-08-25 00:55:29 +02:00
|
|
|
/** @type {string[]} */
|
2023-08-16 01:03:43 +02:00
|
|
|
this.tags
|
2023-08-25 00:55:29 +02:00
|
|
|
/** @type {string[]} */
|
2023-08-16 01:03:43 +02:00
|
|
|
this.genres
|
|
|
|
/** @type {Date} */
|
|
|
|
this.updatedAt
|
|
|
|
/** @type {Date} */
|
|
|
|
this.createdAt
|
|
|
|
}
|
|
|
|
|
|
|
|
static getOldBook(libraryItemExpanded) {
|
|
|
|
const bookExpanded = libraryItemExpanded.media
|
|
|
|
let authors = []
|
|
|
|
if (bookExpanded.authors?.length) {
|
2024-05-29 00:24:02 +02:00
|
|
|
authors = bookExpanded.authors.map((au) => {
|
2023-08-16 01:03:43 +02:00
|
|
|
return {
|
|
|
|
id: au.id,
|
|
|
|
name: au.name
|
|
|
|
}
|
|
|
|
})
|
|
|
|
} else if (bookExpanded.bookAuthors?.length) {
|
2024-05-29 00:24:02 +02:00
|
|
|
authors = bookExpanded.bookAuthors
|
|
|
|
.map((ba) => {
|
|
|
|
if (ba.author) {
|
|
|
|
return {
|
|
|
|
id: ba.author.id,
|
|
|
|
name: ba.author.name
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Logger.error(`[Book] Invalid bookExpanded bookAuthors: no author`, ba)
|
|
|
|
return null
|
2023-07-29 01:03:31 +02:00
|
|
|
}
|
2024-05-29 00:24:02 +02:00
|
|
|
})
|
|
|
|
.filter((a) => a)
|
2023-07-05 01:14:44 +02:00
|
|
|
}
|
|
|
|
|
2023-08-16 01:03:43 +02:00
|
|
|
let series = []
|
|
|
|
if (bookExpanded.series?.length) {
|
2024-05-29 00:24:02 +02:00
|
|
|
series = bookExpanded.series.map((se) => {
|
2023-08-16 01:03:43 +02:00
|
|
|
return {
|
|
|
|
id: se.id,
|
|
|
|
name: se.name,
|
|
|
|
sequence: se.bookSeries.sequence
|
2023-07-05 01:14:44 +02:00
|
|
|
}
|
|
|
|
})
|
2023-08-16 01:03:43 +02:00
|
|
|
} else if (bookExpanded.bookSeries?.length) {
|
2024-05-29 00:24:02 +02:00
|
|
|
series = bookExpanded.bookSeries
|
|
|
|
.map((bs) => {
|
|
|
|
if (bs.series) {
|
|
|
|
return {
|
|
|
|
id: bs.series.id,
|
|
|
|
name: bs.series.name,
|
|
|
|
sequence: bs.sequence
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Logger.error(`[Book] Invalid bookExpanded bookSeries: no series`, bs)
|
|
|
|
return null
|
2023-08-16 01:03:43 +02:00
|
|
|
}
|
2024-05-29 00:24:02 +02:00
|
|
|
})
|
|
|
|
.filter((s) => s)
|
2023-07-05 01:14:44 +02:00
|
|
|
}
|
|
|
|
|
2023-08-16 01:03:43 +02:00
|
|
|
return {
|
|
|
|
id: bookExpanded.id,
|
|
|
|
libraryItemId: libraryItemExpanded.id,
|
|
|
|
coverPath: bookExpanded.coverPath,
|
|
|
|
tags: bookExpanded.tags,
|
|
|
|
audioFiles: bookExpanded.audioFiles,
|
|
|
|
chapters: bookExpanded.chapters,
|
|
|
|
ebookFile: bookExpanded.ebookFile,
|
|
|
|
metadata: {
|
|
|
|
title: bookExpanded.title,
|
|
|
|
subtitle: bookExpanded.subtitle,
|
|
|
|
authors: authors,
|
|
|
|
narrators: bookExpanded.narrators,
|
|
|
|
series: series,
|
|
|
|
genres: bookExpanded.genres,
|
|
|
|
publishedYear: bookExpanded.publishedYear,
|
|
|
|
publishedDate: bookExpanded.publishedDate,
|
|
|
|
publisher: bookExpanded.publisher,
|
|
|
|
description: bookExpanded.description,
|
|
|
|
isbn: bookExpanded.isbn,
|
|
|
|
asin: bookExpanded.asin,
|
|
|
|
language: bookExpanded.language,
|
|
|
|
explicit: bookExpanded.explicit,
|
|
|
|
abridged: bookExpanded.abridged
|
2023-07-05 01:14:44 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-16 01:03:43 +02:00
|
|
|
/**
|
2024-05-29 00:24:02 +02:00
|
|
|
* @param {object} oldBook
|
2023-08-16 01:03:43 +02:00
|
|
|
* @returns {boolean} true if updated
|
|
|
|
*/
|
|
|
|
static saveFromOld(oldBook) {
|
|
|
|
const book = this.getFromOld(oldBook)
|
|
|
|
return this.update(book, {
|
|
|
|
where: {
|
|
|
|
id: book.id
|
|
|
|
}
|
|
|
|
})
|
2024-05-29 00:24:02 +02:00
|
|
|
.then((result) => result[0] > 0)
|
|
|
|
.catch((error) => {
|
|
|
|
Logger.error(`[Book] Failed to save book ${book.id}`, error)
|
|
|
|
return false
|
|
|
|
})
|
2023-08-16 01:03:43 +02:00
|
|
|
}
|
2023-07-05 01:14:44 +02:00
|
|
|
|
2023-08-16 01:03:43 +02:00
|
|
|
static getFromOld(oldBook) {
|
|
|
|
return {
|
|
|
|
id: oldBook.id,
|
|
|
|
title: oldBook.metadata.title,
|
|
|
|
titleIgnorePrefix: oldBook.metadata.titleIgnorePrefix,
|
|
|
|
subtitle: oldBook.metadata.subtitle,
|
|
|
|
publishedYear: oldBook.metadata.publishedYear,
|
|
|
|
publishedDate: oldBook.metadata.publishedDate,
|
|
|
|
publisher: oldBook.metadata.publisher,
|
|
|
|
description: oldBook.metadata.description,
|
|
|
|
isbn: oldBook.metadata.isbn,
|
|
|
|
asin: oldBook.metadata.asin,
|
|
|
|
language: oldBook.metadata.language,
|
|
|
|
explicit: !!oldBook.metadata.explicit,
|
|
|
|
abridged: !!oldBook.metadata.abridged,
|
|
|
|
narrators: oldBook.metadata.narrators,
|
|
|
|
ebookFile: oldBook.ebookFile?.toJSON() || null,
|
|
|
|
coverPath: oldBook.coverPath,
|
|
|
|
duration: oldBook.duration,
|
2024-05-29 00:24:02 +02:00
|
|
|
audioFiles: oldBook.audioFiles?.map((af) => af.toJSON()) || [],
|
2023-08-16 01:03:43 +02:00
|
|
|
chapters: oldBook.chapters,
|
|
|
|
tags: oldBook.tags,
|
|
|
|
genres: oldBook.metadata.genres
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-22 22:53:05 +02:00
|
|
|
getAbsMetadataJson() {
|
|
|
|
return {
|
|
|
|
tags: this.tags || [],
|
2024-05-29 00:24:02 +02:00
|
|
|
chapters: this.chapters?.map((c) => ({ ...c })) || [],
|
2023-10-22 22:53:05 +02:00
|
|
|
title: this.title,
|
|
|
|
subtitle: this.subtitle,
|
2024-05-29 00:24:02 +02:00
|
|
|
authors: this.authors.map((a) => a.name),
|
2023-10-22 22:53:05 +02:00
|
|
|
narrators: this.narrators,
|
2024-05-29 00:24:02 +02:00
|
|
|
series: this.series.map((se) => {
|
2023-10-22 22:53:05 +02:00
|
|
|
const sequence = se.bookSeries?.sequence || ''
|
|
|
|
if (!sequence) return se.name
|
|
|
|
return `${se.name} #${sequence}`
|
|
|
|
}),
|
|
|
|
genres: this.genres || [],
|
|
|
|
publishedYear: this.publishedYear,
|
|
|
|
publishedDate: this.publishedDate,
|
|
|
|
publisher: this.publisher,
|
|
|
|
description: this.description,
|
|
|
|
isbn: this.isbn,
|
|
|
|
asin: this.asin,
|
|
|
|
language: this.language,
|
|
|
|
explicit: !!this.explicit,
|
|
|
|
abridged: !!this.abridged
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-16 01:03:43 +02:00
|
|
|
/**
|
|
|
|
* Initialize model
|
2024-05-29 00:24:02 +02:00
|
|
|
* @param {import('../Database').sequelize} sequelize
|
2023-08-16 01:03:43 +02:00
|
|
|
*/
|
|
|
|
static init(sequelize) {
|
2024-05-29 00:24:02 +02:00
|
|
|
super.init(
|
|
|
|
{
|
|
|
|
id: {
|
|
|
|
type: DataTypes.UUID,
|
|
|
|
defaultValue: DataTypes.UUIDV4,
|
|
|
|
primaryKey: true
|
2023-08-16 01:03:43 +02:00
|
|
|
},
|
2024-05-29 00:24:02 +02:00
|
|
|
title: DataTypes.STRING,
|
|
|
|
titleIgnorePrefix: DataTypes.STRING,
|
|
|
|
subtitle: DataTypes.STRING,
|
|
|
|
publishedYear: DataTypes.STRING,
|
|
|
|
publishedDate: DataTypes.STRING,
|
|
|
|
publisher: DataTypes.STRING,
|
|
|
|
description: DataTypes.TEXT,
|
|
|
|
isbn: DataTypes.STRING,
|
|
|
|
asin: DataTypes.STRING,
|
|
|
|
language: DataTypes.STRING,
|
|
|
|
explicit: DataTypes.BOOLEAN,
|
|
|
|
abridged: DataTypes.BOOLEAN,
|
|
|
|
coverPath: DataTypes.STRING,
|
|
|
|
duration: DataTypes.FLOAT,
|
|
|
|
|
|
|
|
narrators: DataTypes.JSON,
|
|
|
|
audioFiles: DataTypes.JSON,
|
|
|
|
ebookFile: DataTypes.JSON,
|
|
|
|
chapters: DataTypes.JSON,
|
|
|
|
tags: DataTypes.JSON,
|
|
|
|
genres: DataTypes.JSON
|
|
|
|
},
|
|
|
|
{
|
|
|
|
sequelize,
|
|
|
|
modelName: 'book',
|
|
|
|
indexes: [
|
|
|
|
{
|
|
|
|
fields: [
|
|
|
|
{
|
|
|
|
name: 'title',
|
|
|
|
collate: 'NOCASE'
|
|
|
|
}
|
|
|
|
]
|
|
|
|
},
|
|
|
|
// {
|
|
|
|
// fields: [{
|
|
|
|
// name: 'titleIgnorePrefix',
|
|
|
|
// collate: 'NOCASE'
|
|
|
|
// }]
|
|
|
|
// },
|
|
|
|
{
|
|
|
|
fields: ['publishedYear']
|
|
|
|
}
|
|
|
|
// {
|
|
|
|
// fields: ['duration']
|
|
|
|
// }
|
|
|
|
]
|
|
|
|
}
|
|
|
|
)
|
2023-08-16 01:03:43 +02:00
|
|
|
}
|
|
|
|
}
|
2023-07-05 01:14:44 +02:00
|
|
|
|
2024-05-29 00:24:02 +02:00
|
|
|
module.exports = Book
|