mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-10-27 11:18:14 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			326 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			326 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| const { DataTypes, Model } = require('sequelize')
 | |
| const Logger = require('../Logger')
 | |
| 
 | |
| /**
 | |
|  * @typedef EBookFileObject
 | |
|  * @property {string} ino
 | |
|  * @property {string} ebookFormat
 | |
|  * @property {number} addedAt
 | |
|  * @property {number} updatedAt
 | |
|  * @property {{filename:string, ext:string, path:string, relPath:strFing, size:number, mtimeMs:number, ctimeMs:number, birthtimeMs:number}} metadata
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * @typedef ChapterObject
 | |
|  * @property {number} id
 | |
|  * @property {number} start
 | |
|  * @property {number} end
 | |
|  * @property {string} title
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * @typedef SeriesExpandedProperties
 | |
|  * @property {{sequence:string}} bookSeries
 | |
|  *
 | |
|  * @typedef {import('./Series') & SeriesExpandedProperties} SeriesExpanded
 | |
|  *
 | |
|  * @typedef BookExpandedProperties
 | |
|  * @property {import('./Author')[]} authors
 | |
|  * @property {SeriesExpanded[]} series
 | |
|  *
 | |
|  * @typedef {Book & BookExpandedProperties} BookExpanded
 | |
|  */
 | |
| 
 | |
| /**
 | |
|  * @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
 | |
|  */
 | |
| 
 | |
| class Book extends Model {
 | |
|   constructor(values, options) {
 | |
|     super(values, options)
 | |
| 
 | |
|     /** @type {string} */
 | |
|     this.id
 | |
|     /** @type {string} */
 | |
|     this.title
 | |
|     /** @type {string} */
 | |
|     this.titleIgnorePrefix
 | |
|     /** @type {string} */
 | |
|     this.subtitle
 | |
|     /** @type {string} */
 | |
|     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
 | |
|     /** @type {string[]} */
 | |
|     this.narrators
 | |
|     /** @type {AudioFileObject[]} */
 | |
|     this.audioFiles
 | |
|     /** @type {EBookFileObject} */
 | |
|     this.ebookFile
 | |
|     /** @type {ChapterObject[]} */
 | |
|     this.chapters
 | |
|     /** @type {string[]} */
 | |
|     this.tags
 | |
|     /** @type {string[]} */
 | |
|     this.genres
 | |
|     /** @type {Date} */
 | |
|     this.updatedAt
 | |
|     /** @type {Date} */
 | |
|     this.createdAt
 | |
|   }
 | |
| 
 | |
|   static getOldBook(libraryItemExpanded) {
 | |
|     const bookExpanded = libraryItemExpanded.media
 | |
|     let authors = []
 | |
|     if (bookExpanded.authors?.length) {
 | |
|       authors = bookExpanded.authors.map((au) => {
 | |
|         return {
 | |
|           id: au.id,
 | |
|           name: au.name
 | |
|         }
 | |
|       })
 | |
|     } else if (bookExpanded.bookAuthors?.length) {
 | |
|       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
 | |
|           }
 | |
|         })
 | |
|         .filter((a) => a)
 | |
|     }
 | |
| 
 | |
|     let series = []
 | |
|     if (bookExpanded.series?.length) {
 | |
|       series = bookExpanded.series.map((se) => {
 | |
|         return {
 | |
|           id: se.id,
 | |
|           name: se.name,
 | |
|           sequence: se.bookSeries.sequence
 | |
|         }
 | |
|       })
 | |
|     } else if (bookExpanded.bookSeries?.length) {
 | |
|       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
 | |
|           }
 | |
|         })
 | |
|         .filter((s) => s)
 | |
|     }
 | |
| 
 | |
|     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
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * @param {object} oldBook
 | |
|    * @returns {boolean} true if updated
 | |
|    */
 | |
|   static saveFromOld(oldBook) {
 | |
|     const book = this.getFromOld(oldBook)
 | |
|     return this.update(book, {
 | |
|       where: {
 | |
|         id: book.id
 | |
|       }
 | |
|     })
 | |
|       .then((result) => result[0] > 0)
 | |
|       .catch((error) => {
 | |
|         Logger.error(`[Book] Failed to save book ${book.id}`, error)
 | |
|         return false
 | |
|       })
 | |
|   }
 | |
| 
 | |
|   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,
 | |
|       audioFiles: oldBook.audioFiles?.map((af) => af.toJSON()) || [],
 | |
|       chapters: oldBook.chapters,
 | |
|       tags: oldBook.tags,
 | |
|       genres: oldBook.metadata.genres
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   getAbsMetadataJson() {
 | |
|     return {
 | |
|       tags: this.tags || [],
 | |
|       chapters: this.chapters?.map((c) => ({ ...c })) || [],
 | |
|       title: this.title,
 | |
|       subtitle: this.subtitle,
 | |
|       authors: this.authors.map((a) => a.name),
 | |
|       narrators: this.narrators,
 | |
|       series: this.series.map((se) => {
 | |
|         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
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Initialize model
 | |
|    * @param {import('../Database').sequelize} sequelize
 | |
|    */
 | |
|   static init(sequelize) {
 | |
|     super.init(
 | |
|       {
 | |
|         id: {
 | |
|           type: DataTypes.UUID,
 | |
|           defaultValue: DataTypes.UUIDV4,
 | |
|           primaryKey: true
 | |
|         },
 | |
|         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']
 | |
|           // }
 | |
|         ]
 | |
|       }
 | |
|     )
 | |
|   }
 | |
| }
 | |
| 
 | |
| module.exports = Book
 |